﻿/* Copyright (C) 2008 - 2011 by John Cronin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.Text;

namespace tysila
{
    class Mangler
    {
        public static bool IncludeModule = true;

        public static Dictionary<Metadata.MethodDefRow, string> stored_mdr_mangles = new Dictionary<Metadata.MethodDefRow, string>();

        public static string Mangle(Metadata.MethodDefRow mdr, Assembler ass)
        {
            if (stored_mdr_mangles.ContainsKey(mdr))
                return stored_mdr_mangles[mdr];
            string ret = Mangle(mdr, Signature.ParseMethodDefSig(mdr.m, mdr.Signature, ass), ass);
            stored_mdr_mangles.Add(mdr, ret);
            return ret;
        }

        public static string MethodOnlyMangle(Metadata.MethodDefRow mdr, Assembler ass)
        {
            return MethodOnlyMangle(mdr, Signature.ParseMethodDefSig(mdr.m, mdr.Signature, ass), ass);
        }

        public static string Mangle(IEnumerable<byte> sig)
        {
            StringBuilder sb = new StringBuilder();
            foreach (byte b in sig)
                sb.Append(b.ToString("X2"));
            return "_S" + sb.ToString().Length + "_" + sb.ToString();
        }

        public static string Mangle(Signature.Param sig, Assembler ass)
        {
            string s = BaseMangle(sig, ass);
            return "_S" + s.Length.ToString() + "_" + s;
        }

        public static string Mangle(Signature.Field sig, Assembler ass)
        {
            string s = BaseMangle(sig, ass);
            return "_S" + s.Length.ToString() + "_" + s;
        }

        public static string BaseMangle(Signature.Field sig, Assembler ass)
        {
            StringBuilder sb = new StringBuilder();

            sb.Append("06");
            sb.Append(BaseMangle(sig.AsParam(ass), ass));

            return sb.ToString();
        }

        public static string BaseMangle(Signature.Param sig, Assembler ass)
        {
            StringBuilder sb = new StringBuilder();

            foreach (Signature.CustomMod cm in sig.CustomMods)
                sb.Append(BaseMangle(cm));

            sb.Append(BaseMangle(sig.Type, ass));

            return sb.ToString();
        }

        public static string BaseMangle(Signature.BaseOrComplexType bct, Assembler ass)
        {
            if (bct is Signature.BaseType)
                return BaseMangle(bct as Signature.BaseType);
            else if (bct is Signature.ComplexType)
                return BaseMangle(bct as Signature.ComplexType, ass);
            else if (bct is Signature.ZeroBasedArray)
                return BaseMangle(bct as Signature.ZeroBasedArray, ass);
            else if (bct is Signature.GenericType)
                return BaseMangle(bct as Signature.GenericType, ass);
            else if (bct is Signature.BoxedType)
                return "51" + BaseMangle(((Signature.BoxedType)bct).Type, ass);
            else if (bct is Signature.ManagedPointer)
                return "10" + BaseMangle(((Signature.ManagedPointer)bct).ElemType, ass);
            else if (bct is Signature.UnmanagedPointer)
                return "0F" + BaseMangle(((Signature.UnmanagedPointer)bct).BaseType, ass);
            else if (bct is Signature.ComplexArray)
                return BaseMangle(bct as Signature.ComplexArray, ass);
            else if (bct is Signature.GenericParam)
                return "13" + ((Signature.GenericParam)bct).ParamNumber.ToString("X2");
            else if (bct is Signature.GenericMethodParam)
                return "1E" + ((Signature.GenericMethodParam)bct).ParamNumber.ToString("X2");
            else
                throw new NotImplementedException();
        }

        public static string BaseMangle(Signature.GenericType gt, Assembler ass)
        {
            StringBuilder sb = new StringBuilder();

            sb.Append("15");
            sb.Append(BaseMangle(gt.GenType, ass));
            sb.Append(gt.GenParams.Count.ToString("X2"));
            foreach (Signature.BaseOrComplexType gp in gt.GenParams)
                sb.Append(BaseMangle(gp, ass));

            return sb.ToString();
        }

        public static string BaseMangle(Signature.ZeroBasedArray zba, Assembler ass)
        {
            StringBuilder sb = new StringBuilder();

            sb.Append("1D");
            sb.Append(BaseMangle(zba.ElemType, ass));

            return sb.ToString();
        }

        public static string BaseMangle(Signature.ComplexArray ca, Assembler ass)
        {
            StringBuilder ret = new StringBuilder();
            ret.Append("14");
            ret.Append(BaseMangle(ca.ElemType, ass));
            ret.Append(BaseMangle(Metadata.CompressInteger(Convert.ToUInt32(ca.Rank))));
            ret.Append(BaseMangle(Metadata.CompressInteger(Convert.ToUInt32(ca.Sizes.Length))));
            foreach (int size in ca.Sizes)
                ret.Append(BaseMangle(Metadata.CompressInteger(Convert.ToUInt32(size))));
            ret.Append(BaseMangle(Metadata.CompressInteger(Convert.ToUInt32(ca.LoBounds.Length))));
            foreach (int lobound in ca.LoBounds)
                ret.Append(BaseMangle(Metadata.CompressInteger(Convert.ToUInt32(lobound))));
            return ret.ToString();
        }

        public static string BaseMangle(byte[] val)
        {
            StringBuilder sb = new StringBuilder();
            foreach (byte b in val)
                sb.Append(b.ToString("X2"));
            return sb.ToString();
        }

        public static string BaseMangle(Signature.BaseType bt)
        {
            return ((byte)((int)bt.Type)).ToString("X2");
        }

        public static string BaseMangle(Signature.ComplexType ct, Assembler ass)
        {
            StringBuilder sb = new StringBuilder();

            if (ct.IsValueType)
                sb.Append("11");
            else
                sb.Append("12");
            Metadata.TypeDefRow tdr = Metadata.GetTypeDef(ct, ass);
            byte[] ci = Metadata.CompressInteger(Convert.ToUInt32(new Metadata.TableIndex(tdr.m, tdr).ToCodedIndex(Metadata.TableIndex.TypeDefOrRef)));
            foreach (byte b in ci)
                sb.Append(b.ToString("X2"));

            return sb.ToString();
        }

        public static string BaseMangle(Signature.CustomMod cm)
        {
            StringBuilder sb = new StringBuilder();

            if (cm.TypeOfCustomMod == Signature.CustomMod.CustomModType.Optional)
                sb.Append("20");
            else
                sb.Append("1F");
            sb.Append(Metadata.CompressInteger(Convert.ToUInt32(cm.Type.ToCodedIndex(Metadata.TableIndex.TypeDefOrRef))));

            return sb.ToString();
        }

        public static string BaseMangle(Signature.Method meth, Assembler ass)
        {
            StringBuilder sb = new StringBuilder();
            List<byte> bret = new List<byte>();
            byte b1 = 0x00;
            if (meth.HasThis)
                b1 |= 0x20;
            if (meth.ExplicitThis)
                b1 |= 0x40;
            if (meth.CallingConvention == Signature.Method.CallConv.Generic)
                b1 |= 0x10;
            else if (meth.CallingConvention == Signature.Method.CallConv.VarArg)
                b1 |= 0x05;
            bret.Add(b1);
            if(meth.CallingConvention == Signature.Method.CallConv.Generic)
                bret.AddRange(Metadata.CompressInteger((uint)meth.GenParamCount));
            bret.AddRange(Metadata.CompressInteger((uint)meth.Params.Count));

            foreach (byte b in bret)
                sb.Append(b.ToString("X2"));

            sb.Append(BaseMangle(meth.RetType, ass));
            foreach (Signature.Param p in meth.Params)
                sb.Append(BaseMangle(p, ass));

            return sb.ToString();
        }

        public static string BaseMangle(Signature.GenericMethod meth, Assembler ass)
        {
            StringBuilder sb = new StringBuilder();

            sb.Append(BaseMangle(meth.GenMethod, ass));

            List<byte> bret = new List<byte>();
            bret.Add(0x0a);
            bret.AddRange(Metadata.CompressInteger((uint)meth.GenParams.Count));

            foreach (byte b in bret)
                sb.Append(b.ToString("X2"));

            foreach (Signature.BaseOrComplexType p in meth.GenParams)
                sb.Append(BaseMangle(p, ass));

            return sb.ToString();
        }

        public static string BaseMangle(Signature.BaseMethod bm, Assembler ass)
        {
            if (bm is Signature.Method)
                return BaseMangle(bm as Signature.Method, ass);
            else if (bm is Signature.GenericMethod)
                return BaseMangle(bm as Signature.GenericMethod, ass);
            else
                throw new NotSupportedException();
        }

        public static string MethodOnlyMangle(Metadata.MethodDefRow mdr, Signature.BaseMethod bmeth, Assembler ass)
        {
            string meth_name = _EncodeString(mdr.Name);
            StringBuilder sb = new StringBuilder();
            sb.Append("_M");
            sb.Append(meth_name.Length);
            sb.Append("_");
            sb.Append(meth_name);
            string bm = BaseMangle(bmeth, ass);
            sb.Append("_S" + bm.Length.ToString() + "_");
            sb.Append(bm);

            return sb.ToString();
        }

        private static string _EncodeString(string p)
        {
            /* Many tools cannot deal with +, - and . characters in labels.
             * 
             * Therefore we encode them to # followed by the hex value (in capitals) of the ascii code of the character
             * We encode # itself too
             */

            if (p.Contains("+") || p.Contains("-") || p.Contains(".") || p.Contains("#"))
            {
                StringBuilder sb = new StringBuilder();
                foreach (char c in p)
                {
                    if ((c == '+') || (c == '-') || (c == '.') || (c == '#'))
                    {
                        byte[] enc = ASCIIEncoding.ASCII.GetBytes(new char[] { c });
                        sb.Append("#");
                        sb.Append(enc[0].ToString("X2"));
                    }
                    else
                        sb.Append(c);
                }
                return sb.ToString();
            }
            return p;
        }

        public static string Mangle(Metadata.TypeDefRow tdr, Signature.Param tsig,
            Metadata.MethodDefRow mdr, Signature.BaseMethod bmeth, Assembler ass)
        { return Mangle(tdr, tsig, ass) + MethodOnlyMangle(mdr, bmeth, ass); }

        public static string Mangle(Assembler.MethodToCompile mtc, Assembler ass)
        { return Mangle(mtc.type, mtc.tsigp , mtc.meth, mtc.msig, ass); }

        public static string Mangle(Metadata.MethodDefRow mdr, Signature.BaseMethod bmeth, Assembler ass)
        {
            // Mangle a method name

            Metadata.TypeDefRow tdr = Metadata.GetOwningType(mdr.m, mdr);
            Signature.Param tsig = new Signature.Param(new Token(tdr), ass);
            return Mangle(tdr, tsig, mdr, bmeth, ass);
        }

        public static string FieldOnlyMangle(Metadata.FieldRow fr, Signature.Field fsig, Assembler ass)
        {
            string fname = _EncodeString(fr.Name);
            StringBuilder sb = new StringBuilder();
            sb.Append("_F");
            sb.Append(fname.Length);
            sb.Append("_");
            sb.Append(fname);
            sb.Append(Mangle(fsig, ass));

            return sb.ToString();
        }
        
        

        public static string FieldOnlyMangle(Metadata.FieldRow fr)
        {
            throw new NotImplementedException("Use Mangle(Assembler.FieldToCompile)");
            /*StringBuilder sb = new StringBuilder();
            sb.Append("_F");
            sb.Append(fr.Name.Length);
            sb.Append("_");
            sb.Append(fr.Name);
            sb.Append(Mangle(fr.Signature));
            return sb.ToString();*/
        }

        public static string Mangle(Metadata.FieldRow fr, Signature.BaseOrComplexType sig, Assembler ass)
        {
            // Mangle a field name

            Metadata.TypeDefRow tdr = Metadata.GetOwningType(fr.m, fr);
            Signature.Param tsig = new Signature.Param(new Token(tdr), ass);

            return Mangle(tdr, tsig, ass) + FieldOnlyMangle(fr);
        }

        public static string Mangle(Assembler.TypeToCompile ttc, Assembler ass)
        { return Mangle(ttc.type, ttc.tsig, ass); }

        public static string Mangle(Metadata m)
        {
            string mod_name = _EncodeString(m.ModuleName);
            StringBuilder sb = new StringBuilder();
            sb.Append("_m");
            sb.Append(mod_name.Length);
            sb.Append("_");
            sb.Append(mod_name);
            return sb.ToString();
        }

        public static string Mangle(Metadata.TypeDefRow tdr, Signature.Param sig, Assembler ass)
        {
            if ((tdr == null) && (ass != null))
            {
                tdr = Metadata.GetTypeDef(sig.Type, ass);
            }

            if (sig.Type is Signature.GenericType)
                tdr = Metadata.GetTypeDef(((Signature.GenericType)sig.Type).GenType, ass);

            string t_name = _EncodeString(tdr.TypeName);
            string t_nspace = _EncodeString(tdr.TypeNamespace);

            StringBuilder sb = new StringBuilder();
            if(IncludeModule)
                sb.Append(Mangle(tdr.m));
            sb.Append("_N");
            sb.Append(t_nspace.Length);
            sb.Append("_");
            sb.Append(t_nspace);
            sb.Append("_T");
            sb.Append(t_name.Length);
            sb.Append("_");
            sb.Append(t_name);

            if (Metadata.IsSpecialType(tdr, sig.Type, ass))
                sb.Append("_Special");
            else
            {
                string sigm = BaseMangle(sig, ass);
                sb.Append("_S");
                sb.Append(sigm.Length.ToString());
                sb.Append("_");
                sb.Append(BaseMangle(sig, ass));
            }

            return sb.ToString();
        }

        public static string Mangle(Assembler.FieldToCompile ftc, Assembler ass)
        {
            //return Mangler.Mangle(ftc.memberof_type, ftc.memberof_tsig.Type) + Mangler.FieldOnlyMangle(ftc.field, ftc.fsig);
            return FieldOnlyMangle(ftc.field, new Signature.Field(ftc.fsig), ass);
        }

        public class DemangledObject
        {
            public enum ObjType { Type, TypeInfo, FieldInfo, Method, MethodInfo, VTable, StaticFields };
            public ObjType Type;
            public object Object;
        }

        public static DemangledObject Demangle(string s, Assembler ass)
        {
            Dictionary<string, string> tokens = _SplitMangledName(s);

            DemangledObject ret = new DemangledObject();

            if (tokens.ContainsKey("TI"))
            {
                ret.Type = DemangledObject.ObjType.TypeInfo;
                ret.Object = _DemangleType(tokens, ass);
            }
            else if (tokens.ContainsKey("FI"))
            {
                ret.Type = DemangledObject.ObjType.FieldInfo;
                ret.Object = _DemangleField(tokens, ass);
            }
            else if (tokens.ContainsKey("MI"))
            {
                ret.Type = DemangledObject.ObjType.MethodInfo;
                ret.Object = _DemangleMethod(tokens, ass);
            }
            else if (tokens.ContainsKey("M"))
            {
                ret.Type = DemangledObject.ObjType.Method;
                ret.Object = _DemangleMethod(tokens, ass);
            }
            else if (tokens.ContainsKey("VT"))
            {
                ret.Type = DemangledObject.ObjType.VTable;
                ret.Object = _DemangleType(tokens, ass);
            }
            else if (tokens.ContainsKey("s"))
            {
                ret.Type = DemangledObject.ObjType.StaticFields;
                ret.Object = _DemangleType(tokens, ass);
            }
            else
            {
                ret.Type = DemangledObject.ObjType.Type;
                ret.Object = _DemangleType(tokens, ass);
            }

            return ret;
        }

        public static Assembler.TypeToCompile DemangleType(string s, Assembler ass)
        {
            Dictionary<string, string> tokens = _SplitMangledName(s);
            return _DemangleType(tokens, ass);
        }

        public static Assembler.MethodToCompile DemangleMethod(string s, Assembler ass)
        {
            Dictionary<string, string> tokens = _SplitMangledName(s);
            return _DemangleMethod(tokens, ass);
        }

        public static Assembler.MethodToCompile _DemangleMethod(Dictionary<string, string> tokens, Assembler ass)
        {
            Assembler.TypeToCompile ttc = _DemangleType(tokens, ass);
            Assembler.MethodToCompile mtc = new Assembler.MethodToCompile(ass);
            mtc.tsigp = ttc.tsig;
            mtc.type = ttc.type;
            Signature.BaseMethod msig = Signature.ParseMangledMethodSig(ttc.type.m, _DecodeSignature(tokens["S0"]), ass);

            bool found = false;
            foreach (Metadata.MethodDefRow mdr in ttc.type.Methods)
            {
                if (mdr.Name == tokens["M"])
                {
                    if (mdr.GetSignature().Method.GenParamCount != msig.Method.GenParamCount)
                        continue;

                    Signature.BaseMethod test_sig = Signature.ResolveGenericMember(mdr.GetSignature(), ttc.tsig.Type, msig, ass);
                    if (Signature.BaseMethodSigCompare(test_sig, msig.Method, ass))
                    {
                        mtc.meth = mdr;
                        found = true;
                        break;
                    }
                }
            }

            if(!found)
                throw new Exception("Method not found: " + tokens["M"] + " within type: " + tokens["T"]);
            //Metadata.MethodDefRow mdr = Metadata.GetMethodDef(ttc.type.m, tokens["M"], ttc.type, msig, ass);
            //mtc.meth = mdr;
            mtc.msig = msig;
            return mtc;
        }

        public static Assembler.FieldToCompile _DemangleField(Dictionary<string, string> tokens, Assembler ass)
        {
            Assembler.TypeToCompile ttc = _DemangleType(tokens, ass);
            Assembler.FieldToCompile ftc = new Assembler.FieldToCompile { _ass = ass };
            ftc.definedin_tsig = ttc.tsig;
            ftc.definedin_type = ttc.type;
            ftc.memberof_tsig = ttc.tsig;
            ftc.memberof_type = ttc.type;
            Signature.Field fsig = Signature.ParseFieldSig(ttc.type.m, _DecodeSignature(tokens["S0"]), ass);
            
            bool found = false;
            foreach (Metadata.FieldRow fr in ftc.definedin_type.Fields)
            {
                if (fr.Name == tokens["F"])
                {
                    if (Signature.ParamCompare(Signature.ResolveGenericParam(fr.GetSignature().AsParam(ass), ttc.tsig.Type, null, ass), fsig.AsParam(ass), ass))
                    {
                        ftc.field = fr;
                        found = true;
                        break;
                    }
                }
            }

            if (!found)
                throw new Exception("Field not found: " + tokens["F"] + " within type: " + tokens["T"]);

            ftc.fsig = fsig;

            return ftc;
        }

        private static Assembler.TypeToCompile _DemangleType(Dictionary<string, string> tokens, Assembler ass)
        {
            Metadata.TypeDefRow tdr = null;

            byte[] sig = _DecodeSignature(tokens["S"]);
            int x = 0;
            Signature.Param tsig = Signature.ParseParamSig(ass.FindAssembly(tokens["m"]), sig, ref x, ass);

            if (tokens["T"].StartsWith("__Array__"))
            {
                Dictionary<string, string> tokens_2 = _SplitMangledName(tokens["T"].Substring(8));
                tdr = ass.CreateArray(tsig, ((Signature.ComplexArray)tsig.Type).Rank, _DemangleType(tokens_2, ass), false).type;
            }
            else if (tokens["T"].StartsWith("__SzArray__"))
            {
                Dictionary<string, string> tokens_2 = _SplitMangledName(tokens["T"].Substring(10));
                tdr = ass.CreateArray(tsig, 1, _DemangleType(tokens_2, ass), false).type;
            }
            else
                tdr = Metadata.GetTypeDef(tokens["m"], tokens["N"], tokens["T"], ass);
            if (tdr == null)
                tdr = Metadata.GetTypeDef(tsig.Type, ass);
            return new Assembler.TypeToCompile { _ass = ass, tsig = tsig, type = tdr };
        }

        private static byte[] _DecodeSignature(string s)
        {
            List<byte> ret = new List<byte>();
            for (int i = 0; i < s.Length; i += 2)
            {
                string b_s = s.Substring(i, 2);
                byte b = Convert.ToByte(b_s, 16);
                ret.Add(b);
            }
            return ret.ToArray();
        }

        private static Dictionary<string, string> _SplitMangledName(string s)
        {
            int i = 0;
            Dictionary<string, string> ret = new Dictionary<string, string>();

            while (i < s.Length)
            {
                if (s[i] != '_')
                    break;
                i++;

                string tag = new string(new char[] { s[i] });
                if (s[i] == 's')
                {
                    ret.Add("s", "");
                    i++;
                    continue;
                }

                i++;
                if (s[i] == 'p')
                {
                    ret.Add("Special", "");
                    i += 7;
                    continue;
                }
                if ((s[i] == 'I') || (s[i] == 'T'))
                {
                    ret.Add(new string(new char[] { s[i - 1], s[i] }), "");
                    i++;
                    continue;
                }
                List<char> c_length = new List<char>();
                while (s[i] != '_')
                {
                    c_length.Add(s[i]);
                    i++;
                }
                i++;
                int length = Int32.Parse(new string(c_length.ToArray()));

                List<char> c_value = new List<char>();
                while (length > 0)
                {
                    if ((s[i] == '#') && (length >= 3))
                    {
                        i++;
                        string s2 = s.Substring(i, 2);
                        c_value.Add((char)Convert.ToByte(s2, 16));
                        length -= 3;
                        i += 2;
                    }
                    else
                    {
                        c_value.Add(s[i]);
                        i++;
                        length--;
                    }
                }

                string orig_tag = tag;
                int suffix = 0;
                while (ret.ContainsKey(tag))
                {
                    tag = orig_tag + suffix.ToString();
                    suffix++;
                }
                ret.Add(tag, new string(c_value.ToArray()));
            }

            return ret;
        }
    }
}
